package org.zsk.fastdev.assign.impl;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.zsk.fastdev.annotations.FieldMap;
import org.zsk.fastdev.assign.Assign;
import org.zsk.fastdev.bean.BeanUtils;
import org.zsk.fastdev.common.StringOpt;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Stream;

/**
 * <p>
 * 流式赋值工具类
 * </p>
 *
 * @author zsk
 * @version 1.0
 * Created on 2020/4/23
 */
@Slf4j
public class AssignByStream implements Assign {
    private final static Logger logger = LoggerFactory.getLogger(AssignByStream.class);
    private final static AssignByStream INSTANCE = new AssignByStream();

    private AssignByStream() {
    }

    public static AssignByStream getInstance() {
        return INSTANCE;
    }

    @Override
    public void assign(Object assignBean, Object assignedBean) {
        assignWithIncludeAndExclude(assignBean, assignedBean, null, null);
    }

    @Override
    public void assign(Object assignBean, Object assignedBean, String includeFieldName) throws Exception {
        Class<?> assignClazz = assignBean.getClass();
        Class<?> assignedClazz = assignedBean.getClass();
        String[] includeFieldNames = includeFieldName.split(",");
        List<String> includeFieldNamesList = new ArrayList<>(includeFieldNames.length);
        Collections.addAll(includeFieldNamesList, includeFieldNames);
        //第一个类的属性参数（值不为null或空）循环复制给第二个类相同属性参数
        includeFieldNamesList.forEach(name -> {
            try {
                Field field = null;
                boolean isNotNull = name.startsWith("$");
                //去除符号"$"
                if (isNotNull) {
                    field = assignClazz.getDeclaredField(name.substring(1));
                } else {
                    field = assignClazz.getDeclaredField(name);
                }
                field.setAccessible(true);
                Object curVal = null;
                String fieldName = "";
                Annotation fieldMapAnn = field.getDeclaredAnnotation(FieldMap.class);
                if (fieldMapAnn != null) {
                    String tempFieldName = (String) (fieldMapAnn.annotationType()
                            .getMethod("mapFieldName").invoke(fieldMapAnn));
                    if (tempFieldName.equals("*")) {
                        fieldName = field.getName();
                    } else if (tempFieldName != null && !"".equals(tempFieldName)) {
                        fieldName = tempFieldName;
                    }
                } else {
                    fieldName = field.getName();
                }
                String methodName = StringOpt.getSetter(fieldName);
                Method curMethod = null;
                try {
                    curMethod = assignedClazz.getDeclaredMethod(methodName, field.getType());
                } catch (NoSuchMethodException e) {
                    logger.debug("方法不存在:" + methodName);
                }
                if (curMethod != null) {
                    logger.debug("当前方法：" + curMethod.getName());
                    curVal = field.get(assignBean);
                    //是否必须赋值参数
                    if (isNotNull && BeanUtils.isLogicalEmptyObject(curVal)) {
                        logger.debug("必须赋值参数无值");
                        throw new IllegalArgumentException("必须赋值参数无值");
                    }
                    if (!BeanUtils.isLogicalEmptyObject(curVal)) {
                        curMethod.invoke(assignedBean, curVal);
                        logger.debug("设值成功：" + curVal);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    @Override
    public void assignWithInclude(Object assignBean, Object assignedBean, String includeFieldName) {
        this.assignWithIncludeAndExclude(assignBean, assignedBean, includeFieldName, null);
    }

    @Override
    public void assignWithExclude(Object assignBean, Object assignedBean, String excludeFieldName) {
        this.assignWithIncludeAndExclude(assignBean, assignedBean, null, excludeFieldName);
    }

    @Override
    public void assignWithIncludeAndExclude(Object assignBean, Object assignedBean, String includeFieldName, String excludeFieldName) {
        Class<?> assignClazz = assignBean.getClass();
        Class<?> assignedClazz = assignedBean.getClass();
        List<Field> assignFields = Arrays.asList(assignClazz.getDeclaredFields());
        //数组转换list
        String[] includeFieldNames = includeFieldName == null ? new String[0] : includeFieldName.split(",");
        String[] excludeFieldNames = excludeFieldName == null ? new String[0] : excludeFieldName.split(",");
        List<String> includeFieldNamesList = new ArrayList<>(includeFieldNames.length);
        List<String> excludeFieldNamesList = new ArrayList<>(excludeFieldNames.length);
        Collections.addAll(includeFieldNamesList, includeFieldNames);
        Collections.addAll(excludeFieldNamesList, excludeFieldNames);

        assignFields.stream()
                .filter(field -> {
                    String curFieldName = field.getName();
                    boolean result = false;
                    log.debug("当前字段名: " + curFieldName);
                    field.setAccessible(true);
                    try {
                        if (StringUtils.isEmpty(field.get(assignBean))) {
                            result = excludeFieldNamesList.remove(curFieldName);
                            if (result) {
                                log.debug("排除字段无值: " + curFieldName);
                                return false;
                            }
                            result = includeFieldNamesList.remove(curFieldName);
                            if (result && StringUtils.isEmpty(field.get(assignBean))) {
                                log.debug("必须包含字段无值: " + curFieldName);
                                throw new IllegalArgumentException("非法参数: 必须包含字段无值" + curFieldName);
                            }
                            return false;
                        } else {
                            result = excludeFieldNamesList.remove(curFieldName);
                            if (result) {
                                field.set(assignBean, null);
                                log.debug("排除字段有值,且被置空: " + curFieldName);
                                return false;
                            }
                            result = includeFieldNamesList.remove(curFieldName);
                            if (result) {
                                log.debug("必须包含字段有值: " + curFieldName);
                            }
                            return true;
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        return false;
                    }
                })
                .forEach(field -> {
                    assignIfNotNull(field, assignedClazz, assignBean, assignedBean);
                });
        //判断过滤条件是否符合
        if (includeFieldNamesList.size() > 0 || excludeFieldNamesList.size() > 0) {
            log.debug("非法参数: 必须包含参数名或排除参数名有误");
            throw new IllegalArgumentException("非法参数: 必须包含参数名或排除参数名有误");
        }

    }

    /**
     * 对非空字段进行相应映射规则赋值
     *
     * @param field         参数
     * @param assignedClazz 被赋值class
     * @param assignBean    赋值实例
     * @param assignedBean  被赋值实例
     */
    private void assignIfNotNull(Field field, Class<?> assignedClazz, Object assignBean, Object assignedBean) {
        //获取映射参数名称
        String fieldName = "";
        Annotation fieldMapAnn = field.getDeclaredAnnotation(FieldMap.class);
        if (fieldMapAnn != null) {
            String tempFieldName = null;
            try {
                tempFieldName = (String) (fieldMapAnn.annotationType()
                        .getMethod(FieldMap.MAP_FIELD_NAME).invoke(fieldMapAnn));
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }
            if ("*".equals(tempFieldName)) {
                fieldName = field.getName();
            } else if (StringUtils.hasText(tempFieldName)) {
                fieldName = tempFieldName;
            }
        } else {
            fieldName = field.getName();
        }
        log.debug("当前映射参数名称: " + fieldName);
        //获取当前映射参数setter方法
        String methodName = StringOpt.getSetter(fieldName);
        Method curSetterMethod = null;
        try {
            curSetterMethod = assignedClazz.getDeclaredMethod(methodName, field.getType());
        } catch (NoSuchMethodException e) {
            logger.debug("方法不存在:" + methodName);
        }
        if (curSetterMethod != null) {
            logger.debug("当前方法：" + curSetterMethod.getName());
            try {
                if (BeanUtils.isLogicalEmptyObject(field.get(assignBean))) {
                    logger.debug("逻辑空,不执行设值");
                } else {
                    curSetterMethod.invoke(assignedBean, field.get(assignBean));
                    logger.debug("设值成功：");
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                logger.debug("设值失败：");
                e.printStackTrace();
            }
        }
    }

    @Override
    public Map<String, Object> buildMapByEntityWithIncludeAndExclude(Object entity, String includeFieldName, String excludeFieldName) {

        Class<?> assignClazz = entity.getClass();
        List<Field> assignFields = Arrays.asList(assignClazz.getDeclaredFields());
        //数组转换list
        String[] includeFieldNames = includeFieldName == null ? new String[0] : includeFieldName.split(",");
        String[] excludeFieldNames = excludeFieldName == null ? new String[0] : excludeFieldName.split(",");
        List<String> includeFieldNamesList = new ArrayList<>(includeFieldNames.length);
        List<String> excludeFieldNamesList = new ArrayList<>(excludeFieldNames.length);
        Collections.addAll(includeFieldNamesList, includeFieldNames);
        Collections.addAll(excludeFieldNamesList, excludeFieldNames);

        Map<String, Object> resultMap = new HashMap<>(includeFieldNames.length);

        assignFields.stream()
                .filter(field -> {
                    String curFieldName = field.getName();
                    boolean result;
                    log.debug("当前字段名: " + curFieldName);
                    field.setAccessible(true);
                    try {
                        if (StringUtils.isEmpty(field.get(entity))) {
                            result = excludeFieldNamesList.remove(curFieldName);
                            if (result) {
                                log.debug("排除字段无值: " + curFieldName);
                                return false;
                            }
                            result = includeFieldNamesList.remove(curFieldName);
                            if (result && StringUtils.isEmpty(field.get(entity))) {
                                log.debug("必须包含字段无值: " + curFieldName);
                                throw new IllegalArgumentException("非法参数: 必须包含字段无值" + curFieldName);
                            }
                            return false;
                        } else {
                            result = excludeFieldNamesList.remove(curFieldName);
                            if (result) {
                                field.set(entity, null);
                                log.debug("排除字段有值,且被置空: " + curFieldName);
                                return false;
                            }
                            result = includeFieldNamesList.remove(curFieldName);
                            if (result) {
                                log.debug("必须包含字段有值: " + curFieldName);
                            }
                            return true;
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        return false;
                    }
                })
                .forEach(field -> {
                    assignIfNotNull(field, entity, resultMap);
                });
        //判断过滤条件是否符合
        if (includeFieldNamesList.size() > 0 || excludeFieldNamesList.size() > 0) {
            log.debug("非法参数: 必须包含参数名或排除参数名有误");
            throw new IllegalArgumentException("非法参数: 必须包含参数名或排除参数名有误");
        }

        return resultMap;
    }

    private void assignIfNotNull(Field field, Object entity, Map<String, Object> resultMap) {
        //获取映射参数名称
        String fieldName = "";
        Annotation fieldMapAnn = field.getDeclaredAnnotation(FieldMap.class);
        if (fieldMapAnn != null) {
            String tempFieldName = null;
            try {
                tempFieldName = (String) (fieldMapAnn.annotationType()
                        .getMethod(FieldMap.MAP_FIELD_NAME).invoke(fieldMapAnn));
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                e.printStackTrace();
            }
            if ("*".equals(tempFieldName)) {
                fieldName = field.getName();
            } else if (StringUtils.hasText(tempFieldName)) {
                fieldName = tempFieldName;
            }
        } else {
            fieldName = field.getName();
        }
        log.debug("当前映射参数名称: " + fieldName);
        try {
            if (BeanUtils.isLogicalEmptyObject(field.get(entity))) {
                logger.debug("逻辑空,不执行设值");
            } else {
                resultMap.put(fieldName, field.get(entity));
                logger.debug("设值成功：");
            }
        } catch (IllegalAccessException e) {
            logger.debug("设值失败：");
            e.printStackTrace();
        }
    }
}
